package com.example.financeapi.config.interceptor;

import com.example.financeapi.config.annotation.PassToken;
import com.example.financeapi.config.annotation.UserLoginToken;
import com.example.financeapi.utils.JWTUtils;
import com.example.financeapi.utils.RedisUtils;
import com.example.financeapi.entity.vo.CheckResult;
import com.example.financeapi.entity.vo.ResultException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.MimeHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * 认证拦截器
 * @author Alvin
 */
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

    private static final boolean globalTokenCheck=true;
    public static final String[] patterns = new String[] { "/fUser/login", "/wx/**","/wechat/**","/wx/miniapp/user/**","/interface-ui","/error"};
    @Autowired
    private RedisUtils redisUtils;

    /**
     *  预处理回调方法,实现处理器的预处理，第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程（如调用下一个拦截器或处理器）或者接着执行
     *  postHandle()和afterCompletion()；false表示流程中断，不会继续调用其他的拦截器或处理器，中断执行。
     * @param request   请求
     * @param response  响应
     * @param handler   值
     * @return          布尔类型
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        /**1.从请求头中获取  **/
        String token = request.getHeader("token");

        /**2.如果不是映射到方法直接通过 **/
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String getRequestURI =request.getRequestURI();
        boolean passFlag =arrStrContainsStr(patterns,getRequestURI);
        if(passFlag){
            return true;
        }

        /**3.检查是否有passtoken注解，有则跳过认证 **/
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }

        /**4.检查有没有需要用户权限的注解 **/
        boolean checkToken = globalTokenCheck || method.isAnnotationPresent(UserLoginToken.class);
        if(checkToken){
            if (method.isAnnotationPresent(UserLoginToken.class)) {
                UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
                checkToken =userLoginToken.required();
            }
            if(checkToken){
                if (StringUtils.isBlank(token)) {
                    log.error("验证失败 token={},response={},code={},msg={}",token,response, JWTUtils.JWT_ERROR_CODE_NULL,"token签名验证不存在，请重新登录");
                    throw new ResultException(JWTUtils.JWT_ERROR_CODE_NULL, "无token信息，请重新登录");
                }
                // 验证token是否合法
                CheckResult checkResult = JWTUtils.validateJWT(token);
                if (checkResult.isSuccess()) {
                    /**权限验证*/
                    String id =JWTUtils.getUserId(token);
                    String getUnionid =redisUtils.get(id);
                    if(StringUtils.isBlank(getUnionid)){
                        log.error("验证失败 getUnionid={},token={},response={},msg={}",getUnionid,token,response,"该用户不存在，请重新登录");
                        throw new ResultException(-1, "该用户不存在！，请重新登录");
                    }
                    //放在全局的ServletContext中，每一个web应用拥有一个ServletContext，是全局对象
                    //把变量放在这里面，在之后什么地方都可以访问
                    request.getServletContext().setAttribute("unionid", getUnionid);
                    return true;
                }else if(checkResult.getErrCode() == JWTUtils.JWT_ERROR_CODE_FAIL){
                    log.error("签名验证不通过 token={},response={},code={},msg={}",token,response, JWTUtils.JWT_ERROR_CODE_FAIL,"token签名验证不通过，请重新登录");
                    throw new ResultException(JWTUtils.JWT_ERROR_CODE_FAIL, "token错误，请重新登录");
                }else if(checkResult.getErrCode() == JWTUtils.JWT_ERROR_CODE_EXPIRE){
                    log.error("签名验证不通过 token={},response={},code={},msg={}",token,response, JWTUtils.JWT_ERROR_CODE_EXPIRE,"token签名过期，请重新登录");
                    throw new ResultException(JWTUtils.JWT_ERROR_CODE_FAIL, "token已失效，请重新登录");
                }else{
                    log.error("签名验证不通过 token={},response={},code={},msg={}",token,response, checkResult.getErrCode(),"其他异常错误，请重新登录");
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 后处理回调方法，实现处理器的后处理（DispatcherServlet进行视图返回渲染之前进行调用）
     * ，此时我们可以通过modelAndView（模型和视图对象）对模型数据进行处理或对视图进行处理，modelAndView也可能为null。
     *
     * @param httpServletRequest  请求
     * @param httpServletResponse 响应
     * @param o                   值
     * @param modelAndView        视图
     * @throws Exception 抛出异常
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 整个请求处理完毕回调方法,该方法也是需要当前对应的Interceptor的preHandle()的返回值为true时才会执行，
     * 也就是在DispatcherServlet渲染了对应的视图之后执行。用于进行资源清理。整个请求处理完毕回调方法。
     * 如性能监控中我们可以在此记录结束时间并输出消耗时间，还可以进行一些资源清理，类似于try-catch-finally中的finally，但仅调用处理器执行链中
     *
     * @param httpServletRequest  请求
     * @param httpServletResponse 响应
     * @param o                   值
     * @param e                   异常对象
     * @throws Exception 抛出异常
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }

    /**
     * 把自己想要的信息设置到header头部，就相当于是在request的请求头部添加一个键值对，保存自己的数据
     * 把变量放在request请求域中，仅可以被这次请求，即同一个requerst使用
     * request.setAttribute("getAttribute", "getAttribute");
     * 把自己的变量放在头部
     * reflectSetHeader(request, "header", "header");
     * @param request
     * @param key
     * @param value
     */
    private void reflectSetHeader(HttpServletRequest request, String key, String value){
        Class<? extends HttpServletRequest> requestClass = request.getClass();
        try {
            Field request1 = requestClass.getDeclaredField("request");
            request1.setAccessible(true);
            Object o = request1.get(request);
            Field coyoteRequest = o.getClass().getDeclaredField("coyoteRequest");
            coyoteRequest.setAccessible(true);
            Object o1 = coyoteRequest.get(o);
            Field headers = o1.getClass().getDeclaredField("headers");
            headers.setAccessible(true);
            MimeHeaders o2 = (MimeHeaders)headers.get(o1);
            o2.removeHeader(key);
            o2.addValue(key).setString(value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 判断数组中是否包含该字符串
     *
     * @param arrStr
     * @param str
     * @return
     */
    public static boolean arrStrContainsStr(String[] arrStr, String str) {
        boolean flag = false;
        if (arrStr.length > 0) {
            List<String> strList = Arrays.asList(arrStr);
            if (strList.size() > 0) {
                if (strList.contains(str)) {
                    flag = true;
                } else {
                    for (int i = 0; i < strList.size(); i++) {
                        if (str.contains(strList.get(i))) {
                            flag = true;
                        }
                    }
                }
            }
        }
        return flag;
    }

}
